package com.ce.util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.ce.sys.menu.view.SysMenuInfo;
import com.ce.util.cache.CacheView;

/**
* 嫦娥软件有限公司 版权所有 2013
* 创建人：andy
* 创建日期：2010-10-22
* 创建时间：下午05:42:13
* 功能描述：如果系统使用集群，则需要在数据库建立缓存版本信息表，
* 			当缓存需要更新时，将数据库中的缓存版本号+1,
*	 		这样在其它集群的系统中，取缓存数据时通过版本号的比较将过期的缓存清除，然后重新生成新的缓存信息
* 			特别说明：如果在系统运行的过程中更改了系统是否使用集群的选项，
* 			则要在每台服务器上分别进行修改才能正常使用
* ==============================================
* 修改历史
* 修改人		修改时间		修改原因
*
* ==============================================
*/
public class CacheUtil {
	
	/**
	 * 系统注册表项缓存
	 */
	public final static String REGEDIT_CACHE = "REGEDIT_CACHE";
	
	/**
	 * 由于使用RegeditUtil来获取是否使用集群时会导致死循环，所以在本类中增加此静态属性
	 * 当清理RegeditUtil的缓存时，通过SQL语句查询将此属性重置
	 */
	private static Boolean useCluster;

	/**
	 * 菜单缓存
	 */
	public final static String MENU_CACHE = "MENU_CACHE";

	/**
	 * 菜单及操作缓存
	 */
	public final static String MENU_OPT_CACHE = "MENU_OPT_CACHE";

	/** 首页上下文缓存  */
	public final static String PORTAL_CONTEXT = "PORTAL_CONTEXT";

	private static String CACHE_TABLE_NAME = "SYS_T_CACHE$VERSION";

	private static HashMap<String,CacheView> cacheMap = new HashMap<String,CacheView>();

	/**
	* 开发人：andy
	* 开发日期: 2013-10-21  上午08:16:30
	* 功能描述: 获取缓存对象，如果没有对应名称的缓存，直接返回NULL
	* 方法的参数和返回值: 
	* @param cacheName	缓存名称
	* @return			缓存对象
	*/
	public static Object getCache(String cacheName){
		if(isUseCluster()){
			CacheView cache = getCacheView(cacheName);
			int newVer = getDbCacheVersion(cacheName);
			if(cache.getVersion()!=newVer){
				cacheMap.remove(cacheName);
			}	
		}
		return getCacheView(cacheName).getCache();
	}
	
	/**
	* 开发人：andy
	* 开发日期: 2013-10-21  上午08:17:12
	* 功能描述: 获取缓存MAP类对象，如果没有对应名称的缓存，直接创建缓存并返回一个new HashMap()
	* 			说明：HashMap是不同步的，但速度较快，效率高，能满足多线程环境下基本的put和get要求，
	* 				如果缓存需要大量的多线程读写及遍历情况，请自行使用HashTable，并做好同步校验
	* 方法的参数和返回值: 
	* @param cacheName	缓存名称
	* @return			缓存对象
	*/
	public static Map<?, ?> getCacheMap(String cacheName){
		Object obj = getCache(cacheName);
		if(obj==null){
			CacheView cache = getCacheView(cacheName);
			obj = new ConcurrentHashMap<Object, Object>();
			cache.setCache(obj);
		}
		return (Map<?, ?>) obj;
	}
	
	/**
	* 开发人：andy
	* 开发日期: 2013-10-21  上午09:21:28
	* 功能描述: 获取缓存SET类对象，如果没有对应名称的缓存，直接创建缓存并返回一个new HashSet()
	* 			说明：HashSet是不同步的，但速度较快，效率高，能满足多线程环境下基本的add和contains要求，
	* 				如果缓存需要大量的多线程读写及遍历情况，请自行对HashSet进行同步处理，并做好同步校验
	* 				对Set实现同步支持：Set s = Collections.synchronizedSet(new HashSet(...));
	* 方法的参数和返回值: 
	* @param cacheName	缓存名称
	* @return			缓存对象
	*/
	public static Set<?> getCacheSet(String cacheName){
		Object obj = getCache(cacheName);
		if(obj==null){
			CacheView cache = getCacheView(cacheName);
			obj = Collections.synchronizedSet(new HashSet<Object>());
			cache.setCache(obj);
		}
		return (Set<?>) obj;
	}
	
	/**
	* 开发人：andy
	* 开发日期: 2013-10-21  上午09:22:42
	* 功能描述: 获取缓存SET类对象，如果没有对应名称的缓存，直接创建缓存并返回一个new ArrayList()
	* 方法的参数和返回值: 
	* @param cacheName	缓存名称
	* @return			缓存对象
	*/
	public static Collection<?> getCacheList(String cacheName){
		Object obj = getCache(cacheName);
		if(obj==null){
			CacheView cache = getCacheView(cacheName);
			obj = Collections.synchronizedList(new ArrayList<Object>());
			cache.setCache(obj);
		}
		return (Collection<?>) obj;
	}
	
	/**
	* 开发人：andy
	* 开发日期: 2013-10-21  上午10:06:19
	* 功能描述: 设置缓存对象，设置时，如果本机缓存库中没有此缓存，则应该从统一的数据库中取到缓存的版本号
	* 方法的参数和返回值: 
	* @param cacheName
	* @param cacheObject
	*/
	public static void setCache(String cacheName,Object cacheObject){
		CacheView cache = getCacheView(cacheName);
		if(isUseCluster()&&cache.getVersion()==0){//如果使用集群，就要考虑缓存版本问题，版本为0时，则将版本置为1，否则不变更版本号
			int ver = getDbCacheVersion(cacheName);
			cache.setVersion(ver);
		}
		cache.setCache(cacheObject);
	}
	
	/**
	* 开发人：andy
	* 开发日期: 2013-10-22  上午10:33:30
	* 功能描述: 清除缓存
	* 方法的参数和返回值: 
	* @param cacheName
	*/
	public static void clearCache(String cacheName){
		if(REGEDIT_CACHE.equals(cacheName)){//如果更新Regedit缓存，将useCluster置空，下次使用时重新从数据库中查询
			useCluster = null;
		}
		if(cacheMap.containsKey(cacheName)){
			addCacheVersion(cacheName);
		}
		cacheMap.remove(cacheName);
	}
	
	private static CacheView getCacheView(String cacheName){
		CacheView cache = cacheMap.get(cacheName);
		if(cache==null){
			cache = new CacheView(cacheName);
			if(isUseCluster()){
				int newVer = getDbCacheVersion(cacheName);
				cache.setVersion(newVer);
			}
			cacheMap.put(cacheName, cache);
		}
		return cache;
	}

	static long lastLoadDbVersion=0;
	static int lastDbVersion=0;
	/**
	* 开发人：andy
	* 开发日期: 2013-10-22  上午10:33:17
	* 功能描述: 获取缓存的版本号
	* 方法的参数和返回值: 
	* @param cacheName
	* @return
	*/
	private static int getDbCacheVersion(String cacheName){
		int ver = 1;
		if(isUseCluster()){
			if(System.currentTimeMillis()-lastLoadDbVersion<2000){//每两秒更新一次数据
				return lastDbVersion;
			}
			else{
				lastLoadDbVersion = System.currentTimeMillis();
				ver = DbUtil.getFirstIntSqlQuery("select cache_version from "+CACHE_TABLE_NAME+" where cache_name='"+cacheName+"'");
				if(ver==0){
					ver = 1;
					if(DbUtil.getFirstIntSqlQuery("select count(*) from "+CACHE_TABLE_NAME+" where cache_name='"+cacheName+"'")==0){
						DbUtil.executeSqlUpdate("insert into "+CACHE_TABLE_NAME+"(cache_name,cache_version) values(?,?)",
								new Object[]{cacheName,ver});
					}
				}
				lastDbVersion = ver;
			}
		}
		return ver;
	}

	/**
	* 开发人：andy
	* 开发日期: 2013-10-22  上午10:29:21
	* 功能描述: 将对应缓存的版本号+1
	* 方法的参数和返回值: 
	* @param cacheName
	*/
	private static void addCacheVersion(String cacheName){
		CacheView cache = getCacheView(cacheName);
		cache.addVersion();
		if(isUseCluster()){
			DbUtil.executeSqlUpdate("update "+CACHE_TABLE_NAME+" set cache_version=cache_version+1 where cache_name=?",
					new Object[]{cacheName});
		}
	}
	
	/**
	* 开发人：andy
	* 开发日期: 2013-10-22  上午10:28:53
	* 功能描述: 通过系统配置项判断是否使用集群
	* 方法的参数和返回值: 
	* @return
	*/
	private static boolean isUseCluster(){
		if(useCluster==null){
			useCluster = "true".equals(DbUtil.getFirstStringSqlQuery("select vc_value from sys_t_regedit where vc_name = '"+RegeditUtil.SYSTEM_USE_CLUSTER+"'"));
		}
		return useCluster;
	}
	
	public static Set<String> getCacheNames(){
		return cacheMap.keySet();
	}

	/**
	 * 开发人： andy
	 * 开发时间： 2013-6-24 上午09:15:37
	 * 功能描述：获取缓存的内存占用总字节数
	 * 方法的参数和返回值
	 * @return
	 * int 
	 */
	public static int getCacheSize(){
		return getObjectSize(cacheMap);
	}

	/**
	 * 开发人： andy
	 * 开发时间： 2013-6-24 上午09:16:06
	 * 功能描述：获取指定名称缓存的内存占用字节数
	 * 方法的参数和返回值
	 * @param cacheName
	 * @return
	 * int 
	 */
	public static int getCacheSize(String cacheName){
		Object obj = getCache(cacheName);
		return getObjectSize(obj);
	}

	public static int getCacheCnt(String cacheName){
		Object obj = getCache(cacheName);
		if(obj instanceof List){
			return ((List<?>)obj).size();
		}
		else if(obj instanceof Map){
			return ((Map<?, ?>)obj).size();
		}
		else if(obj instanceof Set){
			return ((Set<?>)obj).size();
		}
		return getObjectSize(obj);
	}

	/**
	 * 开发人： andy
	 * 开发时间： 2013-6-24 上午09:16:28
	 * 功能描述：获取指定对象的内存占用字节数
	 * 方法的参数和返回值
	 * @param obj
	 * @return
	 * int 
	 */
	public static int getObjectSize(Object obj){
		return new Occupy().occupyof(obj);
	}
	
}

//一个引用：4字节
//一个Object：8字节
//一个Integer：16字节 == (8 + 4) / 8 * 8
//一个int：4字节
//长度为0的数组大小：JRo64=24,  Sun32=12
//引用大小，如Object = null：                    JRo64=JRo32=4, Sun32=4, Sun64=8
//无成员的对象大小，如new Object();：     JRo32=JRo64=8, Sun32=8, Sun64=16
//new byte[0]:                        JRo32=JRo64=8+8 Sun32=8+4, Sun64=16+8
//长度l的byte数组：(l+19)/8*8
//长度l的char/short数组：(l*2+19)/8*8 == (l+9)/4*8
//长度l的String：(l+1)/4*8+40
//长度l的int数组：(l*4+19)/8*8 ==(l+4)/2*8
//长度l的long数组：(l*8+19)/8*8 == (l+2)*8
/**
 * 计算Java对象占用内存空间大小
 */
class Occupy {
	// 这8个方法不写不行，否则occupyof(int x)会自动重载到occupyof(Object o),并且无法在方法中判断
	public static int occupyof(boolean variable) {
		return 1;
	}

	public static int occupyof(byte variable) {
		return 1;
	}

	public static int occupyof(short variable) {
		return 2;
	}

	public static int occupyof(char variable) {
		return 2;
	}

	public static int occupyof(int variable) {
		return 4;
	}

	public static int occupyof(float variable) {
		return 4;
	}

	public static int occupyof(long variable) {
		return 8;
	}

	public static int occupyof(double variable) {
		return 8;
	}

	public Occupy() {
		this((byte)4,(byte)8,(byte)4);
	}

	public Occupy(byte nullReferenceSize, byte emptyObjectSize,
			byte emptyArrayVarSize) {
		this.NULL_REFERENCE_SIZE = nullReferenceSize;
		this.EMPTY_OBJECT_SIZE = emptyObjectSize;
		this.EMPTY_ARRAY_VAR_SIZE = emptyArrayVarSize;
	}

	public static Occupy forJRockitVM() {
		return new Occupy((byte) 4, (byte) 8, (byte) 8);
	}

	public static Occupy forSun32BitsVM() {
		return new Occupy((byte) 4, (byte) 8, (byte) 4);
	}

	public static Occupy forSun64BitsVM() {
		return new Occupy((byte) 8, (byte) 16, (byte) 8);
	}

	public static Occupy forDetectedVM() {
		return null;
	}

	private final byte NULL_REFERENCE_SIZE;
	private final byte EMPTY_OBJECT_SIZE;
	private final byte EMPTY_ARRAY_VAR_SIZE;

	private static class ref {
		public ref(Object obj) {
			this.obj = obj;
		}

		final Object obj;

		@Override
		public boolean equals(Object obj) {
			return (obj instanceof ref) && ((ref) obj).obj == this.obj;
		}

		@Override
		public int hashCode() {
			return obj.hashCode();
		}
	}

	private List<ref> dedup = new ArrayList<ref>();

	/**
	 * 对象占用的内存空间，对象占用空间与对象的大小并不相等，就好象Windows下文件一样(大小为1字节时占用空间4k)
	 * 
	 * @param object
	 * @return
	 */
	public int occupyof(Object object) {
		dedup.clear();
		return occupyof0(object);
	}

	private int occupyof0(Object object) {
		if (object == null)
			return 0;
		ref r = new ref(object);
		if (dedup.contains(r))
			return 0;
		dedup.add(r);
		int varSize = 0;// 对象中的值类型、引用类型变量大小
		int objSize = 0;// 对象中的引用类型指向的对象实例的大小
		for (Class<?> clazz = object.getClass(); clazz != Object.class; clazz = clazz
				.getSuperclass()) {
			// System.out.println(clazz);
			if (clazz.isArray()) {// 当前对象的数组
				varSize += EMPTY_ARRAY_VAR_SIZE;
				Class<?> componentType = clazz.getComponentType();
				if (componentType.isPrimitive()) {// 当前数组是原生类型的数组
					varSize += lengthOfPrimitiveArray(object)
							* sizeofPrimitiveClass(componentType);
					return occupyOfSize(EMPTY_OBJECT_SIZE, varSize, 0);
				}
				Object[] array = (Object[]) object;
				varSize += NULL_REFERENCE_SIZE * array.length;// 当前数组有length个引用，每个占用4字节
				for (Object o : array)
					objSize += occupyof0(o);
				return occupyOfSize(EMPTY_OBJECT_SIZE, varSize, objSize);
			}
			Field[] fields = clazz.getDeclaredFields();
			for (Field field : fields) {
				if (Modifier.isStatic(field.getModifiers()))
					continue;// 类成员不计
				// System.out.println(field.getDeclaringClass());
				if (clazz != field.getDeclaringClass())
					continue;
				Class<?> type = field.getType();
				if (type.isPrimitive())
					varSize += sizeofPrimitiveClass(type);
				else {
					varSize += NULL_REFERENCE_SIZE;// 一个引用型变量占用4个字节
					try {
						field.setAccessible(true);// 可以访问非public类型的变量
						objSize += occupyof0(field.get(object));
					} catch (Exception e) {
						objSize += occupyofConstructor(object, field);
					}
				}
			}
		}
		return occupyOfSize(EMPTY_OBJECT_SIZE, varSize, objSize);
	}

	public static int sizeof(boolean variable) {
		return 1;
	}

	public static int sizeof(byte variable) {
		return 1;
	}

	public static int sizeof(short variable) {
		return 2;
	}

	public static int sizeof(char variable) {
		return 2;
	}

	public static int sizeof(int variable) {
		return 4;
	}

	public static int sizeof(float variable) {
		return 4;
	}

	public static int sizeof(long variable) {
		return 8;
	}

	public static int sizeof(double variable) {
		return 8;
	}

	/**
	 * 对象的大小
	 * 
	 * @param object
	 * @return
	 */
	public int sizeof(Object object) {
		if (object == null)
			return 0;
		int size = EMPTY_OBJECT_SIZE;
		Class<?> clazz = object.getClass();
		if (clazz.isArray()) {
			size += EMPTY_ARRAY_VAR_SIZE;// length变量是int型
			Class<?> componentType = clazz.getComponentType();
			if (componentType.isPrimitive())
				return size + lengthOfPrimitiveArray(object)
						* sizeofPrimitiveClass(componentType);
			Object[] array = (Object[]) object;
			size += 4 * array.length;
			for (Object o : array)
				size += sizeof(o);
			return size;
		}
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			if (Modifier.isStatic(field.getModifiers()))
				continue;// 类成员不计
			Class<?> type = field.getType();
			if (type.isPrimitive())
				size += sizeofPrimitiveClass(type);
			else {
				size += 4;// 一个引用型变量占用4个字节
				try {
					field.setAccessible(true);// 可以访问非public类型的变量
					size += sizeof(field.get(object));
				} catch (Exception e) {
					size += sizeofConstructor(object, field);
				}
			}
		}
		return size;
	}

	private static int occupyofConstructor(Object object, Field field) {
		throw new UnsupportedOperationException(
				"field type Constructor not accessible: " + object.getClass()
						+ " field:" + field);
	}

	private static int sizeofConstructor(Object object, Field field) {
		throw new UnsupportedOperationException(
				"field type Constructor not accessible: " + object.getClass()
						+ " field:" + field);
	}

	/**
	 * 对象的大小 和 占用空间并不相等，就好象Windows下文件一样(大小为1字节时占用空间4k)
	 * 对象占用空间的增长以8个字节为单位，占用空间=大小对8的无条件进位法， 即occupy = (size + 8 - 1) / 8 * 8; 例如：
	 * 大小8字节：占用8字节，(new Object()就是占用8字节) 大小9字节：占用16字节 大小16字节：占用16字节
	 * 大小17字节：占用24字节
	 * 
	 * @param size
	 *            大小，以字节为单位
	 * @return 占用空间
	 */
	private static int occupyOfSize(int size) {
		return (size + 7) / 8 * 8;
	}

	private static int occupyOfSize(int selfSize, int varsSize, int objsSize) {
		// System.out.println("self=" + selfSize + " vars=" + varsSize +
		// " objs=" + objsSize);
		return occupyOfSize(selfSize) + occupyOfSize(varsSize) + objsSize;
	}

	private static int sizeofPrimitiveClass(Class<?> clazz) {
		return clazz == boolean.class || clazz == byte.class ? 1
				: clazz == char.class || clazz == short.class ? 2
						: clazz == int.class || clazz == float.class ? 4 : 8;
	}

	private static int lengthOfPrimitiveArray(Object object) {
		Class<?> clazz = object.getClass();
		return clazz == boolean[].class ? ((boolean[]) object).length
				: clazz == byte[].class ? ((byte[]) object).length
						: clazz == char[].class ? ((char[]) object).length
								: clazz == short[].class ? ((short[]) object).length
										: clazz == int[].class ? ((int[]) object).length
												: clazz == float[].class ? ((float[]) object).length
														: clazz == long[].class ? ((long[]) object).length
																: ((double[]) object).length;
	}
}
